/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: client.c,v 1.10 2005/11/21 00:36:12 pullmoll Exp $
 *****************************************************************************/
#include "client.h"
#include "tools.h"
#include "sha1.h"
#include "fec.h"
#include "logger.h"

/* Clients don't seem to like Pending/EndMessage, so disable for now */
#define	NODE_PENDING	1

pid_t g_client_pid = (pid_t)-1;

#define	LINESIZE	4096

#define	FCP_FREENET	0
#define	FCP_ENTROPY	1
typedef struct fcp_proto_s {
	char header[4];
	int proto;
	int keep_alive;
	int verbose;
	char uri[256];
}	fcp_proto_t;

/* fixed settings for entropy_fec_8_4 algorithm */
#define	ALGO_NAME		"entropy_fec_8_4"
#define	DATA_BLOCKS		8
#define	CHECK_BLOCKS	4
#define	TOTAL_BLOCKS	(DATA_BLOCKS+CHECK_BLOCKS)

/* structures to keep FCP 1.1 spec data */
typedef struct segment_header_s {
	char fec_algorithm[32];
	uint64_t file_length;
	uint64_t offset;
	size_t block_count;
	size_t block_size;
	size_t check_block_count;
	size_t check_block_size;
	size_t segments;
	size_t segment_num;
	size_t blocks_required;
}	segment_header_t;

/* For now we only support FEC with fixed 8 data and 4 check blocks */
typedef struct block_map_s {
	chkey_t block[8];
	chkey_t check[4];
}	block_map_t;

#define	SIZE_KB(n)	((uint64_t)1024*(n))
#define	SIZE_MB(n)	((uint64_t)1024*1024*(n))
#define	SIZE_GB(n)	((uint64_t)1024*1024*1024*(n))

static int boolean(const char *val)
{
	if (0 == strcasecmp(val, "true") ||
		0 == strcasecmp(val, "yes") ||
		0 == strcasecmp(val, "1")) {
		return 1;
	}
	return 0;
}

static const char *ull(uint64_t val)
{
	static char buff[8][64];
	static int which;
	which = (which + 1) % 8;
	pm_snprintf(buff[which], 64, "%llx", (unsigned long long)val);
	return buff[which];
}

int node_format_error(conn_t *conn, const char *fmt, ...)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *buff = NULL;
	va_list ap;
	int rc = 0;
	FUN("node_format_error");
	(void)fcp;

	buff = (char *)xmalloc(LINESIZE);

	LOGS(L_CLIENT,L_ERROR,("NODE: FormatError\n"));
	if (0 != (rc = sock_printf(conn, "FormatError\n"))) {
		goto bailout;
	}

	va_start(ap, fmt);
	pm_vsnprintf(buff, LINESIZE, fmt, ap);
	va_end(ap);

	LOGS(L_CLIENT,L_ERROR,("NODE: %s\n", buff));
	if (0 != (rc = sock_printf(conn, buff))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_ERROR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn, "EndMessage\n"))) {
		goto bailout;
	}

bailout:
	xfree(buff);
	return rc;
}

int node_failed(conn_t *conn, const char *fmt, ...)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *buff = NULL;
	va_list ap;
	int rc;
	FUN("node_failed");
	(void)fcp;

	buff = (char *)xmalloc(LINESIZE);

	LOGS(L_CLIENT,L_ERROR,("NODE: Failed\n"));
	if (0 != (rc = sock_printf(conn, "Failed\n"))) {
		goto bailout;
	}

	va_start(ap, fmt);
	pm_vsnprintf(buff, LINESIZE, fmt, ap);
	va_end(ap);

	LOGS(L_CLIENT,L_ERROR,("NODE: %s\n", buff));
	if (0 != (rc = sock_printf(conn, buff))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_ERROR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn, "EndMessage\n"))) {
		goto bailout;
	}

bailout:
	xfree(buff);
	return rc;
}

int node_hello(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	uint64_t val;
	int rc;
	FUN("node_hello");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: NodeHello\n"));
	if (0 != (rc = sock_printf(conn,"NodeHello\n"))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Node=%s,%s,%s,%s\n",
		NODE_NAME, NODE_VER_MAJ, NODE_VER_MIN, NODE_BUILD));
	if (0 != (rc = sock_printf(conn,"Node=%s,%s,%s,%s\n",
		NODE_NAME, NODE_VER_MAJ, NODE_VER_MIN, NODE_BUILD))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Protocol=%s\n", NODE_PROTO));
	if (0 != (rc = sock_printf(conn,"Protocol=%s\n", NODE_PROTO))) {
		goto bailout;
	}
	if (g_conf->latest_build > strtoul(NODE_BUILD, NULL, 0)) {
		char build[32];
		pm_snprintf(build, sizeof(build), "%u", g_conf->latest_build);
		LOGS(L_CLIENT,L_MINOR,("NODE: HighestSeenBuild=%s\n",build));
		if (0 != (rc = sock_printf(conn,"HighestSeenBuild=%s\n",build))) {
			goto bailout;
		}
	}
	val = (uint64_t)2 * 1024 * 1024 * 1024;
	if (val > g_store->storesize) {
		val = g_store->storesize;
	}
	val = val - 1;
	LOGS(L_CLIENT,L_MINOR,("NODE: MaxFileSize=%s\n", ull(val)));
	if (0 != (rc = sock_printf(conn,"MaxFileSize=%s\n", ull(val)))) {
		goto bailout;
	}
	if (FCP_ENTROPY == fcp->proto) {
		val = g_conf->maxhtl;
		LOGS(L_CLIENT,L_MINOR,("NODE: MaxHopsToLive=%s\n", ull(val)));
		if (0 != (rc = sock_printf(conn,"MaxHopsToLive=%s\n", ull(val)))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: SVKExtension=%s\n", URI_SVKEXT));
		if (0 != (rc = sock_printf(conn,"SVKExtension=%s\n", URI_SVKEXT))) {
			goto bailout;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		goto bailout;
	}

bailout:
	return rc;
}

int client_hello(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *var, *val, *eol;
	int rc = 0;
	FUN("client_hello");
	(void)fcp;

	/* simply scan for EndMessage */
	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s=%s\n", var, val));
		}
		xfree(line);
	}

	/* if reading went ok, now send the NodeHello */
	if (0 == rc) {
		rc = node_hello(conn);
	}

	xfree(line);
	return rc;
}

/************************************
 *  Sample output of Fred:
 *  NodeInfo
 *  ActiveJobs=2e
 *  MostRecentTimestamp=f56e716b9a
 *  LeastRecentTimestamp=f405f80b24
 *  AllocatedMemory=2716000
 *  Architecture=x86
 *  JavaName=Java HotSpot(TM) Client VM
 *  JavaVendor=http://java.sun.com/
 *  NodePort=7c30
 *  NodeAddress=217.82.234.148
 *  MaxFileSize=28f5c2
 *  DatastoreUsed=fe64000
 *  AvailableThreads=13
 *  EstimatedLoad=26
 *  DatastoreMax=10000000
 *  JavaVersion=1.4.1-beta-b14
 *  DatastoreFree=19c000
 *  OperatingSystemVersion=5.0
 *  OperatingSystem=Windows 2000
 *  FreeMemory=9b368
 *  IsTransient=false
 *  RoutingTime=a
 *  EndMessage
 ************************************/

int node_info(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	peer_load_t pl;
	shm_pool_stats_t st;
	struct utsname name;
	const char *nodeaddress;
	const char *str;
	uint64_t val;
	int nodeport;
	int rc;
	FUN("node_info");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: NodeInfo\n"));
	if (0 != (rc = sock_printf(conn,"NodeInfo\n"))) {
		goto bailout;
	}
	uname(&name);
	peer_load(&pl);
	sstats(&st);

	LOGS(L_CLIENT,L_MINOR,("NODE: OperatingSystem=%s\n", name.sysname));
	if (0 != (rc = sock_printf(conn,"OperatingSystem=%s\n", name.sysname))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: OperatingSystemVersion=%s\n", name.release));
	if (0 != (rc = sock_printf(conn,"OperatingSystemVersion=%s\n", name.release))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Architecture=%s\n", name.machine));
	if (0 != (rc = sock_printf(conn,"Architecture=%s\n", name.machine))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: IsTransient=%s\n", "FALSE"));
	if (0 != (rc = sock_printf(conn,"IsTransient=%s\n", "FALSE"))) {
		goto bailout;
	}
	nodeaddress = inet_ntoa(g_conf->node.sin_addr);
	LOGS(L_CLIENT,L_MINOR,("NODE: NodeAddress=%s\n", nodeaddress));
	if (0 != (rc = sock_printf(conn,"NodeAddress=%s\n", nodeaddress))) {
		goto bailout;
	}
	nodeport = ntohs(g_conf->node.sin_port);
	LOGS(L_CLIENT,L_MINOR,("NODE: NodePort=%x\n", nodeport));
	if (0 != (rc = sock_printf(conn,"NodePort=%x\n", nodeport))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EstimatedLoad=%x\n", pl.estimated_load));
	if (0 != (rc = sock_printf(conn,"EstimatedLoad=%x\n", pl.estimated_load))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: ActiveJobs=%x\n", pl.active_jobs));
	if (0 != (rc = sock_printf(conn,"ActiveJobs=%x\n", pl.active_jobs))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: AvailableThreads=%x\n", pl.avail_threads));
	if (0 != (rc = sock_printf(conn,"AvailableThreads=%x\n", pl.avail_threads))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: RoutingTime=%x\n", pl.routing_time));
	if (0 != (rc = sock_printf(conn,"RoutingTime=%x\n", pl.routing_time))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: AllocatedMemory=%s\n", ull(st.total)));
	if (0 != (rc = sock_printf(conn,"AllocatedMemory=%s\n", ull(st.total)))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: FreeMemory=%s\n", ull(st.free)));
	if (0 != (rc = sock_printf(conn,"FreeMemory=%s\n", ull(st.free)))) {
		goto bailout;
	}
	val = g_store->storesize;
	LOGS(L_CLIENT,L_MINOR,("NODE: DatastoreMax=%s\n", ull(val)));
	if (0 != (rc = sock_printf(conn,"DatastoreMax=%s\n", ull(val)))) {
		goto bailout;
	}
	val = g_store->currsize;
	LOGS(L_CLIENT,L_MINOR,("NODE: DatastoreUsed=%s\n", ull(val)));
	if (0 != (rc = sock_printf(conn,"DatastoreUsed=%s\n", ull(val)))) {
		goto bailout;
	}
	val = (uint64_t)2 * 1024 * 1024 * 1024;
	if (val > g_store->storesize) {
		val = g_store->storesize;
	}
	val = val -1;
	LOGS(L_CLIENT,L_MINOR,("NODE: MaxFileSize=%s\n", ull(val)));
	if (0 != (rc = sock_printf(conn,"MaxFileSize=%s\n", ull(val)))) {
		goto bailout;
	}
	str = "http://www.gnu.org/";
	LOGS(L_CLIENT,L_MINOR,("NODE: JavaVendor=%s\n", str));
	if (0 != (rc = sock_printf(conn,"JavaVendor=%s\n", str))) {
		goto bailout;
	}
	str = "0.0";
	LOGS(L_CLIENT,L_MINOR,("NODE: JavaVersion=%s\n", str));
	if (0 != (rc = sock_printf(conn,"JavaVersion=%s\n", str))) {
		goto bailout;
	}
	str = "YPOC (Ye Plain Old'e C)";
	LOGS(L_CLIENT,L_MINOR,("NODE: JavaName=%s\n", str));
	if (0 != (rc = sock_printf(conn,"JavaName=%s\n", str))) {
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		goto bailout;
	}

bailout:
	return rc;
}

int client_info(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *var, *val, *eol;
	int rc = 0;
	FUN("client_info");
	(void)fcp;

	/* simply scan for EndMessage */
	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s=%s\n", var, val));
		}
		xfree(line);
	}

	/* if reading went ok, now send the NodeInfo */
	if (0 == rc) {
		rc = node_info(conn);
	}

	xfree(line);
	return rc;
}

int node_uri_error(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_uri_error");
	(void)fcp;

	LOGS(L_CLIENT,L_ERROR,("NODE: URIError\n"));
	if (0 != (rc = sock_printf(conn,"URIError\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_ERROR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}
	return rc;
}

int node_restarted(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_restarted");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: Restarted\n"));
	if (0 != (rc = sock_printf(conn,"Restarted\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}
	return rc;
}

int node_data_not_found(conn_t *conn, const char *reason)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_data_not_found");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: DataNotFound\n"));
	if (0 != (rc = sock_printf(conn,"DataNotFound\n"))) {
		return rc;
	}
	if (NULL != reason) {
		LOGS(L_CLIENT,L_MINOR,("NODE: Reason=%s\n", reason));
		if (0 != (rc = sock_printf(conn,"Reason=%s\n", reason))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}
	return rc;
}

int node_route_not_found(conn_t *conn, const char *reason)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_route_not_found");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: RouteNotFound\n"));
	if (0 != (rc = sock_printf(conn,"RouteNotFound\n"))) {
		return rc;
	}
	if (NULL != reason) {
		LOGS(L_CLIENT,L_MINOR,("NODE: Reason=%s\n", reason));
		if (0 != (rc = sock_printf(conn,"Reason=%s\n", reason))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}
	return rc;
}

int node_data_found(conn_t *conn, size_t datalen, size_t metalen)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_data_found");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: DataFound\n"));
	if (0 != (rc = sock_printf(conn,"DataFound\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: DataLength=%s\n",ull(datalen)));
	if (0 != (rc = sock_printf(conn,"DataLength=%s\n",ull(datalen)))) {
		return rc;
	}
	if (metalen > 0) {
		LOGS(L_CLIENT,L_MINOR,("NODE: MetadataLength=%s\n",ull(metalen)));
		if (0 != (rc = sock_printf(conn,"MetadataLength=%s\n",ull(metalen)))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

int node_data_chunk(conn_t *conn, void *buff, size_t len)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_data_chunk");
	(void)fcp;

	LOGS(L_CLIENT,L_DEBUG,("NODE: DataChunk\n"));
	if (0 != (rc = sock_printf(conn,"DataChunk\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_DEBUG,("NODE: Length=%s\n", ull(len)));
	if (0 != (rc = sock_printf(conn,"Length=%s\n", ull(len)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_DEBUG,("NODE: Data\n"));
	if (0 != (rc = sock_printf(conn,"Data\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_DEBUG,("sending data\n"));
	if (0 != (rc = sock_writeall(conn, buff, len))) {
		return rc;
	}

	return rc;
}

static int get_cb(void *user, file_info_t *info)
{
	conn_t *conn = (conn_t *)user;
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	fd_set rdfds, wrfds, exfds;
	uint64_t val;
	struct timeval tv;
	int rc, sk, pct;
	FUN("get_cb");

	if (INFO_TYPE_RESTARTED == info->type) {
		return node_restarted(conn);
	} else if (0 != fcp->verbose) {
		LOGS(L_CLIENT,L_MINOR,("NODE: NodeGet\n"));
		if (0 != sock_printf(conn, "NodeGet\n")) {
			return -1;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: SHA1=%s\n", sha1_hexstr(&info->hash)));
		if (0 != sock_printf(conn, "SHA1=%s\n", sha1_hexstr(&info->hash))) {
			return -1;
		}
		val = info->offs;
		LOGS(L_CLIENT,L_MINOR,("NODE: Offset=%s\n", ull(val)));
		if (0 != sock_printf(conn, "Offset=%s\n", ull(val))) {
			return -1;
		}
		val = info->size;
		LOGS(L_CLIENT,L_MINOR,("NODE: DataLength=%s\n", ull(val)));
		if (0 != sock_printf(conn, "DataLength=%s\n", ull(val))) {
			return -1;
		}
		pct = (info->size > 0) ?
			(int)((uint64_t)100 * info->offs / info->size) : 0;
		LOGS(L_CLIENT,L_MINOR,("NODE: Percent=%d%%\n", pct));
		if (0 != sock_printf(conn, "Percent=%d%%\n", pct)) {
			return -1;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
		if (0 != sock_printf(conn, "EndMessage\n")) {
			return -1;
		}
	} else {
#if	NODE_PENDING
		if (0 != (rc = node_pending(conn, fcp->uri, NULL, NULL))) {
			return -1;
		}
#else
		rc = 0;
#endif
	}

	sk = conn->socket;
	if (-1 == sk || CONN_CONNECTED != conn->status) {
		LOGS(L_CLIENT,L_DEBUG,("socket was shut down\n"));
		return -1;
	}
	FD_ZERO(&rdfds);
	FD_SET(sk, &rdfds);
	FD_ZERO(&wrfds);
	FD_SET(sk, &wrfds);
	FD_ZERO(&exfds);
	FD_SET(sk, &exfds);
	memset(&tv, 0, sizeof(tv));
	if (select(sk + 1, &rdfds, &wrfds, &exfds, &tv) < 0) {
		LOGS(L_CLIENT,L_DEBUG,("select(%d,...) failed (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	if (FD_ISSET(sk, &exfds)) {
		LOGS(L_CLIENT,L_DEBUG,("sk %d exception (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	return 0;
}

int client_get(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	tempfile_t tfdata, tfmeta;
	char *line = NULL;
	char *uri = NULL;
	char *var, *val, *eol;
	uint32_t datalen = 0, metalen = 0;
	int htl = 0;
	uint8_t *buff = NULL;
	chkey_t key;
	size_t slimit = 0;
	int remove_local_key = 0;
	size_t fcp_size;
	int rc = 0;
	FUN("client_get");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);
	temp_closed(&tfdata, "data", TEMP_UNKNOWN_SIZE);
	temp_closed(&tfmeta, "meta", TEMP_UNKNOWN_SIZE);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var,"EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "URI")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: URI=%s\n", val));
			uri = xstrdup(val);
		} else if (0 == strcasecmp(var, "HopsToLive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: HopsToLive=%s\n", val));
			htl = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "RemoveLocalKey")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: RemoveLocalKey=%s\n", val));
			remove_local_key = boolean(val) ? FILE_DEL : 0;
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "MaxFileSize")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: MaxFileSize=%s\n", val));
			slimit = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "Verbose")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Verbose=%s\n", val));
			fcp->verbose = boolean(val);
		} else {
			LOGS(L_CLIENT,L_ERROR,("CLIENT: unknown message '%s%s%s'\n",
				var, val ? "=" : "", val ? val : ""));
			return node_format_error(conn,
				"Reason=bogus client message %s", line);
		}
		xfree(line);
	}

	if (NULL == uri) {
		LOGS(L_CLIENT,L_ERROR,("URI not specified\n"));
		return node_uri_error(conn);
	}
	LOGS(L_CLIENT,L_MINOR,("checking URI %s\n", uri));
	if (0 != is_valid_uri(&key, uri)) {
		LOGS(L_CLIENT,L_ERROR,("URI is invalid\n"));
		xfree(uri);
		return node_uri_error(conn);
	}

	if (key.type[0] == MSB(K_SSK_P) &&
		key.type[1] == LSB(K_SSK_P)) {
		if (0 != (rc = create_chk_from_ksk(&key,uri))) {
			LOGS(L_CLIENT,L_ERROR,("cannot create CHK from key: %s\n", uri));
			goto bailout;
		}
	}
	switch ((key.type[0] << 8) | key.type[1]) {
	case K_KSK:
		pm_snprintf(fcp->uri, sizeof(fcp->uri), "%s", uri);
		break;
	case K_CHK:
		pm_snprintf(fcp->uri, sizeof(fcp->uri), "%s", key_long(&key));
		break;
	case K_SSK_P:
		pm_snprintf(fcp->uri, sizeof(fcp->uri), "%s", key_long(&key));
		break;
	}
	xfree(uri);

	rc = file_get(&key, &tfdata, &tfmeta, htl, &slimit, conn, get_cb,
		remove_local_key);
	if (0 != rc) {
		if (EHOSTUNREACH == errno) {
			LOGS(L_CLIENT,L_MINOR,("route not found\n"));
			rc = node_route_not_found(conn, "No route found");
		} else if (ETIMEDOUT == errno) {
			LOGS(L_CLIENT,L_MINOR,("timed out\n"));
			rc = node_route_not_found(conn, "Timed out");
		} else if (EFBIG == errno) {
			LOGS(L_CLIENT,L_MINOR,("document too big\n"));
			rc = node_failed(conn,
				"Reason=Document is too big\n" \
				"Datalength=%x\n", slimit);
		} else {
			LOGS(L_CLIENT,L_MINOR,("data not found\n"));
			rc = node_data_not_found(conn, "No data found");
		}
		goto bailout;
	}

	if (0 != (rc = temp_open(&tfdata))) {
		LOGS(L_CLIENT,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfdata.name, strerror(errno)));
		datalen = 0;
	} else {
		datalen = tfdata.size;
	}

	if (0 != (rc = temp_open(&tfmeta))) {
		LOGS(L_CLIENT,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfmeta.name, strerror(errno)));
		metalen = 0;
	} else {
		metalen = tfmeta.size;
	}
	/* The reply datalen includes both, data and metadata */
	datalen = datalen + metalen;

	if (0 != (rc = node_data_found(conn, datalen, metalen))) {
		LOGS(L_CLIENT,L_ERROR,("node_data_found() call failed!\n"));
		goto bailout;
	}

	while (datalen > 0) {
		size_t size, done;
		if (metalen > 0) {
			size = metalen > fcp_size ? fcp_size : metalen;
			if (0 != (rc = temp_read(&tfmeta, buff, size, &done))) {
				LOGS(L_CLIENT,L_ERROR,("temp_read() from %s failed (%s)!\n",
					tfmeta.name, strerror(errno)));
				goto bailout;
			}
			if (0 != (rc = node_data_chunk(conn, buff, done))) {
				LOGS(L_CLIENT,L_ERROR,("node_data_chunk call failed!\n"));
				goto bailout;
			}
			metalen -= done;
		} else {
			size = datalen > fcp_size ? fcp_size : datalen;
			if (0 != (rc = temp_read(&tfdata, buff, size, &done))) {
				LOGS(L_CLIENT,L_ERROR,("temp_read() from %s failed (%s)!\n",
					tfdata.name, strerror(errno)));
				goto bailout;
			}
			if (0 != (rc = node_data_chunk(conn, buff, done))) {
				LOGS(L_CLIENT,L_ERROR,("node_data_chunk call failed!\n"));
				goto bailout;
			}
		}
		datalen -= done;
	}

bailout:
	temp_close(&tfdata, 1);
	temp_close(&tfmeta, 1);
	xfree(buff);
	xfree(line);
	return rc;
}

int client_broadcast(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *channel = NULL;
	char *sender = NULL;
	char *payload = NULL;
	char *var, *val, *eol;
	int htl = 0;
	uint8_t *buff = NULL;
	size_t fcp_size;
	int rc = 0;
	FUN("client_broadcast");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var,"EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "Channel")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Channel=%s\n", val));
			channel = xstrdup(val);
		} else if (0 == strcasecmp(var, "Sender")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Sender=%s\n", val));
			sender = xstrdup(val);
		} else if (0 == strcasecmp(var, "Payload")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Payload=%s\n", val));
			payload = xstrdup(val);
		} else if (0 == strcasecmp(var, "HopsToLive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: HopsToLive=%s\n", val));
			htl = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Verbose")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Verbose=%s\n", val));
			fcp->verbose = boolean(val);
		} else {
			LOGS(L_CLIENT,L_ERROR,("CLIENT: unknown message '%s%s%s'\n",
				var, val ? "=" : "", val ? val : ""));
			return node_format_error(conn,
				"Reason=bogus client message %s", line);
		}
		xfree(line);
	}

	if (NULL == channel) {
		LOGS(L_CLIENT,L_ERROR,("Channel not specified\n"));
		return node_uri_error(conn);
	}
	if (NULL == sender) {
		LOGS(L_CLIENT,L_ERROR,("Sender not specified\n"));
		return node_uri_error(conn);
	}
	if (NULL == payload) {
		LOGS(L_CLIENT,L_ERROR,("Payload not specified\n"));
		return node_uri_error(conn);
	}

	if (0 != (rc = peer_ins_broadcast(channel, sender, payload, htl))) {
		if (EHOSTUNREACH == errno) {
			LOGS(L_CLIENT,L_MINOR,("route not found\n"));
			rc = node_route_not_found(conn, "No route found");
		} else if (ETIMEDOUT == errno) {
			LOGS(L_CLIENT,L_MINOR,("timed out\n"));
			rc = node_route_not_found(conn, "Timed out");
		} else if (EFBIG == errno) {
			LOGS(L_CLIENT,L_MINOR,("broadcast too big\n"));
			rc = node_failed(conn, "Reason=broadcast is too big\n");
		} else {
			LOGS(L_CLIENT,L_MINOR,("data not found\n"));
			rc = node_format_error(conn, "Unknown error");
		}
		goto bailout;
	}

	/* signal success to the client */
	rc = node_success(conn, NULL, NULL, NULL);

bailout:
	xfree(buff);
	xfree(line);
	xfree(channel);
	xfree(sender);
	xfree(payload);
	return rc;
}

int client_receive(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	sha1_digest_t sha1;
	time_t t0;
	char channel[MAXCHANNEL];
	char sender[MAXSENDER];
	char payload[MAXPAYLOAD];
	char *var, *val, *eol;
	int id = 0;
	int pmin = 0;
	int pmax = 0;
	uint8_t *buff = NULL;
	size_t fcp_size;
	int rc = 0;
	FUN("client_broadcast");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var,"EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "ID")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ID=%s\n", val));
			id = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Verbose")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Verbose=%s\n", val));
			fcp->verbose = boolean(val);
		} else {
			LOGS(L_CLIENT,L_ERROR,("CLIENT: unknown message '%s%s%s'\n",
				var, val ? "=" : "", val ? val : ""));
			return node_format_error(conn,
				"Reason=bogus client message %s", line);
		}
		xfree(line);
	}

	if (0 != (rc = peer_get_broadcast(id, &pmin, &pmax, &sha1, &t0, channel, sender, payload))) {
		LOGS(L_CLIENT,L_ERROR,("peer_get_broadcast() call failed!\n"));
		rc = node_failed(conn, "Reason=no broadcasts\n");
		goto bailout;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Success\n"));
	if (0 != (rc = sock_printf(conn, "Success\n"))) {
		goto bailout;
	}
	if (0 == id) {
		LOGS(L_CLIENT,L_MINOR,("NODE: MinID=%x\n", pmin));
		if (0 != (rc = sock_printf(conn, "MinID=%x\n", pmin))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: MaxID=%x\n", pmax));
		if (0 != (rc = sock_printf(conn, "MaxID=%x\n", pmax))) {
			goto bailout;
		}
	} else {
		LOGS(L_CLIENT,L_MINOR,("NODE: ID=%x\n", id));
		if (0 != (rc = sock_printf(conn, "ID=%x\n", id))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: SHA1=%s\n", hexstr(sha1.digest, SHA1SIZE)));
		if (0 != (rc = sock_printf(conn, "SHA1=%s\n", hexstr(sha1.digest, SHA1SIZE)))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: Time=%x\n", (unsigned)t0));
		if (0 != (rc = sock_printf(conn, "Time=%x\n", (unsigned)t0))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: Channel=%s\n", channel));
		if (0 != (rc = sock_printf(conn, "Channel=%s\n", channel))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: Sender=%s\n", sender));
		if (0 != (rc = sock_printf(conn, "Sender=%s\n", sender))) {
			goto bailout;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: Payload=%s\n", payload));
		if (0 != (rc = sock_printf(conn, "Payload=%s\n", payload))) {
			goto bailout;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

bailout:
	xfree(buff);
	xfree(line);
	return rc;
}

int node_key_collision(conn_t *conn, const char *uri,
	const char *svk_public, const char *svk_private)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char svk_public_trim[32];
	size_t len;
	int rc;
	FUN("node_key_collision");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: KeyCollision\n"));
	if (0 != (rc = sock_printf(conn, "KeyCollision\n"))) {
		return rc;
	}
	if (NULL != uri) {
		LOGS(L_CLIENT,L_MINOR,("NODE: URI=%s\n", uri));
		if (0 != (rc = sock_printf(conn, "URI=%s\n", uri))) {
			return rc;
		}
	}
	if (NULL != svk_public) {
		len = snprintf(svk_public_trim, sizeof(svk_public_trim), "%s",
			svk_public);
		/* chop off the BCMA part */
		svk_public_trim[len - 4] = '\0';
		LOGS(L_CLIENT,L_MINOR,("NODE: PublicKey=%s\n", svk_public_trim));
		if (0 != (rc = sock_printf(conn, "PublicKey=%s\n", svk_public_trim))) {
			return rc;
		}
	}
	if (NULL != svk_private) {
		LOGS(L_CLIENT,L_MINOR,("NODE: PrivateKey=%s\n", svk_private));
		if (0 != (rc = sock_printf(conn, "PrivateKey=%s\n", svk_private))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

int node_pending(conn_t *conn, const char *uri,
	const char *svk_public, const char *svk_private)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char svk_public_trim[32];
	size_t len;
	int rc;
	FUN("node_pending");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: Pending\n"));
	if (0 != (rc = sock_printf(conn, "Pending\n"))) {
		return rc;
	}
	if (NULL != uri && strlen(uri) > 0) {
		LOGS(L_CLIENT,L_MINOR,("NODE: URI=%s\n", uri));
		if (0 != (rc = sock_printf(conn, "URI=%s\n", uri))) {
			return rc;
		}
	}
	if (NULL != svk_public) {
		len = pm_snprintf(svk_public_trim, sizeof(svk_public_trim), "%s",
			svk_public);
		/* chop off the BCMA part */
		svk_public_trim[len - 4] = '\0';
		LOGS(L_CLIENT,L_MINOR,("NODE: PublicKey=%s\n", svk_public_trim));
		if (0 != (rc = sock_printf(conn, "PublicKey=%s\n", svk_public_trim))) {
			return rc;
		}
	}
	if (NULL != svk_private) {
		LOGS(L_CLIENT,L_MINOR,("NODE: PrivateKey=%s\n", svk_private));
		if (0 != (rc = sock_printf(conn, "PrivateKey=%s\n", svk_private))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

int node_success(conn_t *conn, const char *uri,
	const char *svk_public, const char *svk_private)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char svk_public_trim[32];
	int rc;
	FUN("node_success");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: Success\n"));
	if (0 != (rc = sock_printf(conn, "Success\n"))) {
		return rc;
	}
	if (NULL != uri) {
		LOGS(L_CLIENT,L_MINOR,("NODE: URI=%s\n", uri));
		if (0 != (rc = sock_printf(conn, "URI=%s\n", uri))) {
			return rc;
		}
	}
	if (NULL != svk_public) {
		if (NULL != svk_private) {
			size_t len;
			len = pm_snprintf(svk_public_trim, sizeof(svk_public_trim), "%s",
				svk_public);
			/* chop off the BCMA part */
			svk_public_trim[len - 4] = '\0';
			LOGS(L_CLIENT,L_MINOR,("NODE: PublicKey=%s\n", svk_public_trim));
			rc = sock_printf(conn, "PublicKey=%s\n", svk_public_trim);
		} else {
			/* InvertPrivateKey reply has to be format: Public=keyBCMA */
			LOGS(L_CLIENT,L_MINOR,("NODE: Public=%s\n", svk_public));
			rc = sock_printf(conn, "PublicKey=%s\n", svk_public);
		}
		if (0 != rc)
			return rc;
	}
	if (NULL != svk_private) {
		LOGS(L_CLIENT,L_MINOR,("NODE: PrivateKey=%s\n", svk_private));
		if (0 != (rc = sock_printf(conn, "PrivateKey=%s\n", svk_private))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

static int put_cb(void *user, file_info_t *info)
{
	conn_t *conn = (conn_t *)user;
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	fd_set rdfds, wrfds, exfds;
	uint64_t val;
	struct timeval tv;
	int rc, sk, pct;
	FUN("put_cb");

	if (INFO_TYPE_RESTARTED == info->type) {
		return node_restarted(conn);
	} else if (0 != fcp->verbose) {
		LOGS(L_CLIENT,L_MINOR,("NODE: NodePut\n"));
		if (0 != sock_printf(conn, "NodePut\n")) {
			return -1;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: SHA1=%s\n", sha1_hexstr(&info->hash)));
		if (0 != sock_printf(conn, "SHA1=%s\n", sha1_hexstr(&info->hash))) {
			return -1;
		}
		val = info->offs;
		LOGS(L_CLIENT,L_MINOR,("NODE: Offset=%s\n", ull(val)));
		if (0 != sock_printf(conn, "Offset=%s\n", ull(val))) {
			return -1;
		}
		val = info->size;
		LOGS(L_CLIENT,L_MINOR,("NODE: DataLength=%s\n", ull(val)));
		if (0 != sock_printf(conn, "DataLength=%s\n", ull(val))) {
			return -1;
		}
		pct = (info->size > 0) ?
			(int)((uint64_t)100 * info->offs / info->size) : 0;
		LOGS(L_CLIENT,L_MINOR,("NODE: Percent=%d%%\n", pct));
		if (0 != sock_printf(conn, "Percent=%d%%\n", pct)) {
			return -1;
		}
		LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
		if (0 != sock_printf(conn, "EndMessage\n")) {
			return -1;
		}
	} else {
#if	NODE_PENDING
		if (0 != (rc = node_pending(conn, fcp->uri, NULL, NULL))) {
			return -1;
		}
#else
		rc = 0;
#endif
	}

	sk = conn->socket;
	if (-1 == sk || CONN_CONNECTED != conn->status) {
		LOGS(L_CLIENT,L_DEBUG,("socket was shut down\n"));
		return -1;
	}
	FD_ZERO(&rdfds);
	FD_SET(sk, &rdfds);
	FD_ZERO(&wrfds);
	FD_SET(sk, &wrfds);
	FD_ZERO(&exfds);
	FD_SET(sk, &exfds);
	memset(&tv, 0, sizeof(tv));
	if (select(sk + 1, &rdfds, &wrfds, &exfds, &tv) < 0) {
		LOGS(L_CLIENT,L_DEBUG,("select(%d,...) failed (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	if (FD_ISSET(sk, &exfds)) {
		LOGS(L_CLIENT,L_DEBUG,("sk %d exception (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	return 0;
}

int client_put(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	tempfile_t tfdata, tfmeta;
	uint8_t *buff = NULL;
	char *line = NULL;
	char *uri = NULL;
	char *pubssk = NULL;
	char *pubfile = NULL;
	char *fquri = NULL;
	char *var, *val, *eol;
	char uri_proto[32];
	uint32_t datalen = 0, metalen = 0;
	int htl = 0;
	chkey_t key;
	size_t size;
	int remove_local_key = 0;
	int metadata_only = 0;
	int partial_store = 0;
	int collision = 0, rc = 0;
	size_t fcp_size;
	FUN("client_put");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}
	buff = (uint8_t *)xmalloc(fcp_size);
	temp_invalid(&tfdata);
	temp_invalid(&tfmeta);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "URI")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: URI=%s\n", val));
			uri = xstrdup(val);
		} else if (0 == strcasecmp(var, "HopsToLive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: HopsToLive=%s\n", val));
			htl = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "MetadataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: MetadataLength=%s\n", val));
			metalen = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "RemoveLocalKey")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: RemoveLocalKey=%s\n", val));
			remove_local_key = boolean(val) ? FILE_DEL : 0;
		} else if (0 == strcasecmp(var, "MetadataOnly")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: MetadataOnly=%s\n", val));
			metadata_only = boolean(val) ? FILE_META : 0;
		} else if (0 == strcasecmp(var, "PartialStore")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: PartialStore=%s\n", val));
			partial_store = boolean(val) ? FILE_PART : 0;
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Verbose")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Verbose=%s\n", val));
			fcp->verbose = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}

	/* FIXME: Keep the protocol type 'freenet:' for (some) clients */
	if (0 == strncmp(uri, "freenet:", 8)) {
		LOGS(L_CLIENT,L_MINOR,("+++ uri_proto is 'freenet:'\n"));
		strcpy(uri_proto, "freenet:");
	} else {
		strcpy(uri_proto, URI_PROTO);
	}

	LOGS(L_CLIENT,L_MINOR,("got datalen=%x, metalen=%x, htl=%x\n",
		(unsigned)datalen, (unsigned)metalen, (unsigned)htl));

	if (0 != (rc = temp_binary(&tfdata, "data", datalen - metalen))) {
		LOGS(L_CLIENT,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfdata.name, strerror(errno)));
		goto bailout;
	}

	if (0 != (rc = temp_binary(&tfmeta, "meta", metalen))) {
		LOGS(L_CLIENT,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfmeta.name, strerror(errno)));
		goto bailout;
	}
	
	/* now receive the data */
	while (datalen > 0) {
		if (metalen > 0) {
			size = (metalen > fcp_size) ? fcp_size : metalen;
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %#x bytes\n", (unsigned)size));
			if (0 != (rc = temp_write(&tfmeta, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("temp_write() to %s failed (%s)\n",
					tfmeta.name, strerror(errno)));
				goto bailout;
			}
			metalen -= size;
		} else {
			size = (datalen > fcp_size) ? fcp_size : datalen;
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %#x bytes\n", (unsigned)size));
			if (0 != (rc = temp_write(&tfdata, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("temp_write() to %s failed (%s)\n",
					tfdata.name, strerror(errno)));
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("wrote %#x bytes data to '%s'\n",
				(unsigned)size, tfdata.name));
		}
		datalen -= size;
	}
	temp_close(&tfdata, 0);
	temp_close(&tfmeta, 0);
	LOGS(L_CLIENT,L_MINOR,("done receiving (meta)data from client\n"));

	if (0 != is_chk(uri) && 0 != is_ssk(uri)) {
		chkey_t chk;
		if (0 != (rc = create_chk_from_ksk(&chk, uri))) {
			LOGS(L_CLIENT,L_ERROR,("'%s' is not a valid KSK@ key!\n", uri));
			node_failed(conn, "Reason=invalid KSK@ '%s'\n", uri);
			errno = EINVAL;
			rc = -1;
			goto bailout;
		}
		rc = file_get(&chk, NULL, NULL, htl, NULL, NULL, NULL, 0);
		if (0 == rc) {
			LOGS(L_CLIENT,L_MINOR,("KSK@ collision for '%s'\n", uri));
			collision = 1;
			rc = 0;
			goto skipinsert;
		}
		pm_snprintf(fcp->uri, sizeof(fcp->uri), "%s", key_long(&chk));
	} else {
		pm_snprintf(fcp->uri, sizeof(fcp->uri), "%s", uri);
	}

	rc = file_put(&key, &tfdata, &tfmeta, htl, &collision, conn, put_cb,
		remove_local_key | metadata_only | partial_store);
	if (0 == rc) {
		if (0 == collision) {
			LOGS(L_CLIENT,L_MINOR,("New file sent successfully\n"));
		} else {
			LOGS(L_CLIENT,L_MINOR,("Existing file sent successfully\n"));
		}
	} else if (EEXIST == errno) {
		LOGS(L_CLIENT,L_MINOR,("Existing file sent successfully\n"));
		rc = 0;
	} else if (EHOSTUNREACH == errno) {
		LOGS(L_CLIENT,L_MINOR,("No route found\n"));
		node_route_not_found(conn, "No route found");
		rc = -1;
	} else if (ETIMEDOUT == errno) {
		LOGS(L_CLIENT,L_MINOR,("Timed out\n"));
		node_route_not_found(conn, "Timed out");
		rc = -1;
	} else {
		const char *errmsg = strerror(errno);
		LOGS(L_CLIENT,L_MINOR,("Could not insert data (%s)\n",
			errmsg));
		node_failed(conn, "Reason=Could not insert data (%s)\n",
			errmsg);
		rc = -1;
	}
	if (0 != rc) {
		goto bailout;
	}

	/* is this a entropy:CHK@ (complete or placeholder) */
	if (0 == (rc = is_chk(uri))) {

		LOGS(L_CLIENT,L_MINOR,("client put a CHK@ '%s'\n", uri));
		xfree(uri);
		fquri = create_uri_from_chk(&key);
		LOGS(L_CLIENT,L_MINOR,("URI %s\n", fquri));

	} else if (0 == (rc == is_ssk(uri))) {
		chkey_t chk = key;	/* save the content hash key */
		char *slash;

		LOGS(L_CLIENT,L_MINOR,("client put a SSK@ '%s'\n", uri));
		if (NULL == (slash = strchr(uri, '/'))) {
			LOGS(L_CLIENT,L_ERROR,("no slash after SSK@ key\n"));
			rc = node_failed(conn, "Reason=no slash after SSK@ '%s'\n", uri);
			goto bailout;
		}
		if (0 != (rc = is_valid_ssk(&key, uri))) {
			LOGS(L_CLIENT,L_ERROR,("'%s' is not a valid SSK@ key!\n", uri));
			node_failed(conn, "Reason=invalid SSK@ '%s'\n", uri);
			errno = EINVAL;
			rc = -1;
			goto bailout;
		}
		pubssk = create_uri_from_ssk(&key);
		pubfile = xstrdup(slash);
		xfree(uri);
		size = strlen(pubssk) + strlen(pubfile) + 1;
		uri = (char *)xcalloc(size, sizeof(char));
		pm_snprintf(uri, size, "%s%s", pubssk, pubfile);

		LOGS(L_CLIENT,L_MINOR,("public key SSK@ '%s'\n", uri));
		rc = file_put_redirect(&key, &chk, uri, &fquri, htl, &collision,
			remove_local_key | metadata_only | partial_store);
		if (0 == rc) {
			if (0 == collision) {
				LOGS(L_CLIENT,L_MINOR,("New URI %s redirects to %s\n",
					fquri, key_short(&chk)));
			} else {
				LOGS(L_CLIENT,L_MINOR,("Existing URI %s redirects to %s\n",
					fquri, key_short(&chk)));
			}
		} else if (EHOSTUNREACH == errno) {
			LOGS(L_CLIENT,L_MINOR,("No route found\n"));
			rc = node_route_not_found(conn, "No route found");
			rc = -1;
		} else if (ETIMEDOUT == errno) {
			LOGS(L_CLIENT,L_MINOR,("Timed out\n"));
			rc = node_route_not_found(conn, "Timed out");
			rc = -1;
		} else {
			LOGS(L_CLIENT,L_ERROR,("file_put_redirect('%s') call failed!\n",
				uri));
		}
		if (0 != rc) {
			goto bailout;
		}
	} else {
		chkey_t chk = key;	/* save the content hash key */

		LOGS(L_CLIENT,L_MINOR,("client put a KSK@ '%s'\n", uri));
		rc = file_put_redirect(&key, &chk, uri, &fquri, htl, &collision,
			remove_local_key | metadata_only | partial_store);
		if (0 == rc) {
			if (0 == collision) {
				LOGS(L_CLIENT,L_MINOR,("New URI %s redirects to %s\n",
					fquri, key_long(&chk)));
			} else {
				LOGS(L_CLIENT,L_MINOR,("Existing URI %s redirects to %s\n",
					fquri, key_long(&chk)));
			}
		} else if (EHOSTUNREACH == errno) {
			LOGS(L_CLIENT,L_MINOR,("No route found\n"));
			rc = node_route_not_found(conn, "No route found");
			rc = -1;
		} else if (ETIMEDOUT == errno) {
			LOGS(L_CLIENT,L_MINOR,("Timed out\n"));
			rc = node_route_not_found(conn, "Timed out");
			rc = -1;
		} else {
			LOGS(L_CLIENT,L_ERROR,("file_put_redirect('%s') call failed!\n",
				uri));
		}
		if (0 != rc) {
			goto bailout;
		}
	}

skipinsert:
	if (0 == rc) {
		if (0 != remove_local_key) {
			LOGS(L_CLIENT,L_MINOR,("removing local copy of %s\n",
				key_short(&key)));
			rc = file_get(&key, NULL, NULL, 0, NULL, NULL, NULL, FILE_DEL);
		}
		if (0 != strcmp(uri_proto, URI_PROTO)) {
			char *bcma;
			LOGS(L_CLIENT,L_MINOR,("+++ changing final uri proto to %s\n",
				uri_proto));
			/* FIXME: Overwrite the proto part of this URI */
			memcpy(fquri, uri_proto, strlen(uri_proto));
			/* FIXME: change SSK public key extension from BCMA/ to PAgM/ */
			if (NULL != (bcma = strstr(fquri, URI_SVKEXT "/"))) {
				memcpy(bcma, URI_SVKEXT_ALT, strlen(URI_SVKEXT_ALT));
				LOGS(L_CLIENT,L_MINOR,("+++ faking URI type to %s\n",
					URI_SVKEXT_ALT));
			}
		}
		if (0 == collision) {
			rc = node_success(conn, fquri, NULL, NULL);
		} else {
			rc = node_key_collision(conn, fquri, NULL, NULL);
		}
	} else {
		rc = node_failed(conn, "Reason=Error creating '%s' (%d)\n", uri, rc);
	}

bailout:
	temp_close(&tfdata, 1);
	temp_close(&tfmeta, 1);
	xfree(buff);
	xfree(line);
	xfree(pubfile);
	xfree(pubssk);
	xfree(fquri);
	xfree(uri);
	return rc;
}

int client_chk(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	tempfile_t tfdata, tfmeta;
	uint8_t *buff = NULL;
	char *line = NULL;
	char *uri = NULL;
	char *pubssk = NULL;
	char *pubfile = NULL;
	char *fquri = NULL;
	char *var, *val, *eol;
	uint32_t datalen = 0, metalen = 0;
	int htl;
	chkey_t key;
	size_t size;
	int collision = 0;
	int rc = 0;
	size_t fcp_size;
	FUN("client_chk");

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "URI")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: URI=%s\n", val));
			uri = xstrdup(val);
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "MetadataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: MetadataLength=%s\n", val));
			metalen = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Verbose")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Verbose=%s\n", val));
			fcp->verbose = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}

	htl = -1;
	pm_snprintf(fcp->uri, sizeof(fcp->uri), "CHK@");

	LOGS(L_CLIENT,L_MINOR,("got datalen=%x, metalen=%x, htl=%x\n",
		(unsigned)datalen, (unsigned)metalen, (unsigned)htl));

	if (datalen > metalen) {
		if (0 != (rc = temp_binary(&tfdata, "data", datalen - metalen))) {
			LOGS(L_CLIENT,L_ERROR,("temp_binary('%s',0x%x) call failed (%s)\n",
				tfdata.name, datalen - metalen, strerror(errno)));
			goto bailout;
		}
	}
	if (metalen > 0) {
		if (0 != (rc = temp_binary(&tfmeta, "meta", metalen))) {
			LOGS(L_CLIENT,L_ERROR,("temp_binary('%s',0x%x) call failed (%s)\n",
				tfmeta.name, metalen, strerror(errno)));
			goto bailout;
		}
	}
	
	/* now receive the data */
	while (datalen > 0) {
		if (metalen > 0) {
			size = (metalen > fcp_size) ? fcp_size : metalen;
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %#x bytes\n", (unsigned)size));
			if (0 != (rc = temp_write(&tfmeta, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("temp_write() to '%s' failed (%s)\n",
					tfmeta.name, strerror(errno)));
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("wrote %#x bytes metadata to '%s'\n",
				(unsigned)size, tfmeta.name));
			metalen -= size;
		} else {
			size = (datalen > fcp_size) ? fcp_size : datalen;
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %#x bytes\n", (unsigned)size));
			if (0 != (rc = temp_write(&tfdata, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("temp_write() to %s failed (%s)\n",
					tfdata.name, strerror(errno)));
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("wrote %#x bytes data to '%s'\n",
				(unsigned)size, tfdata.name));
		}
		datalen -= size;
	}
	temp_close(&tfdata, 0);
	temp_close(&tfmeta, 0);
	LOGS(L_CLIENT,L_MINOR,("done receiving (meta)data from client\n"));

	rc = file_put(&key, &tfdata, &tfmeta, htl, &collision, conn, put_cb, 0);
	if (0 == rc) {
		if (0 == collision) {
			LOGS(L_CLIENT,L_MINOR,("New key hashed successfully\n"));
		} else {
			LOGS(L_CLIENT,L_MINOR,("Exisiting key hashed successfully\n"));
		}
	} else if (EEXIST == errno) {
		LOGS(L_CLIENT,L_MINOR,("Existing file hashed successfully\n"));
		rc = 0;
	} else if (EHOSTUNREACH == errno) {
		LOGS(L_CLIENT,L_MINOR,("No route found\n"));
		node_route_not_found(conn, "No route found");
		rc = -1;
	} else if (ETIMEDOUT == errno) {
		LOGS(L_CLIENT,L_MINOR,("Timed out\n"));
		node_route_not_found(conn, "Timed out");
		rc = -1;
	} else {
		const char *errmsg = strerror(errno);
		LOGS(L_CLIENT,L_MINOR,("Could not insert data (%s)\n",
			errmsg));
		node_failed(conn, "Reason=Could not insert data (%s)\n",
			errmsg);
		rc = -1;
	}
	if (0 != rc) {
		goto bailout;
	}

	fquri = create_uri_from_chk(&key);
	LOGS(L_CLIENT,L_MINOR,("URI %s\n", fquri));

	if (0 == rc) {
		rc = node_success(conn, fquri, NULL, NULL);
	} else {
		rc = node_failed(conn, "Reason=Error creating '%s' (%d)\n", uri, rc);
	}

bailout:
	temp_close(&tfdata, 1);
	temp_close(&tfmeta, 1);
	xfree(buff);
	xfree(line);
	xfree(pubfile);
	xfree(pubssk);
	xfree(fquri);
	xfree(uri);
	return rc;
}

int client_del(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *uri = NULL;
	char *fquri = NULL;
	char *var, *val, *eol;
	chkey_t key;
	int rc = 0;
	FUN("client_del");
	(void)fcp;

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "URI")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: URI=%s\n", val));
			uri = xstrdup(val);
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}

	if (NULL == uri) {
		rc = node_failed(conn, "Reason=No URI specified\n");
		goto bailout;
	} else if (0 != is_valid_uri(&key, uri)) {
		LOGS(L_CLIENT,L_ERROR,("URI is invalid\n"));
		xfree(uri);
		return node_uri_error(conn);
	} else {
		LOGS(L_CLIENT,L_MINOR,("got key %s\n", key_short(&key)));
	}

	fquri = create_uri_from_chk(&key);
	if (0 == (rc = store_zap(&key.sha1))) {
		LOGS(L_CLIENT,L_MINOR,("Existing key deleted\n"));
	} else if (EEXIST == errno) {
		LOGS(L_CLIENT,L_MINOR,("Non-existing key deleted\n"));
		rc = 0;
	} else if (EHOSTUNREACH == errno) {
		LOGS(L_CLIENT,L_MINOR,("No route found\n"));
		node_route_not_found(conn, "No route found");
		rc = -1;
	} else if (ETIMEDOUT == errno) {
		LOGS(L_CLIENT,L_MINOR,("Timed out\n"));
		node_route_not_found(conn, "Timed out");
		rc = -1;
	} else {
		const char *errmsg = strerror(errno);
		LOGS(L_CLIENT,L_MINOR,("Could not insert data (%s)\n",
			errmsg));
		node_failed(conn, "Reason=Could not insert data (%s)\n",
			errmsg);
		rc = -1;
	}
	if (0 != rc) {
		goto bailout;
	}

	fquri = create_uri_from_chk(&key);

	if (0 == rc) {
		rc = node_success(conn, fquri, NULL, NULL);
	} else {
		rc = node_failed(conn, "Reason=Error deleting '%s' (%d)\n", uri, rc);
	}

bailout:
	xfree(line);
	xfree(fquri);
	xfree(uri);
	return rc;
}

int client_generate_svk_pair(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *var, *val, *eol;
	int rc = 0;
	FUN("client_generate_svk_pair");
	(void)fcp;

	/* simply scan for EndMessage */
	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else {
			if (NULL != var) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: %s=%s\n", var, val));
			} else {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
				rc = -1;
				goto bailout;
			}
		}
		xfree(line);
	}

	/* if reading went ok, generate key pair and send the Success message */
	if (0 == rc) {
		chkey_t pub, priv;
		if (0 != (rc = key_svk_pair_create(&pub, &priv))) {
			LOGS(L_CLIENT,L_ERROR,("key_ssk_create() call failed\n"));
			rc = node_failed(conn,"Reason=SVK Key pair creation failed");
			goto bailout;
		}
		rc = node_success(conn, NULL, key_str(&pub), key_str(&priv));
	}

bailout:
	xfree(line);
	return rc;
}

int client_invert_private_key(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *private = NULL;
	char *var, *val, *eol;
	int rc = 0;
	FUN("client_invert_private_key");

	/* scan for the Private= and EndMessage lines */
	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "Private")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Private=%s\n", val));
			xfree(private);
			private = xstrdup(val);
		} else if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else {
			if (NULL != var) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: %s=%s\n", var, val));
			} else {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
				rc = -1;
				goto bailout;
			}
		}
		xfree(line);
	}

	/* if reading went ok, invert the private key and send a success message */
	if (0 == rc) {
		chkey_t pub;
		if (NULL == private) {
			rc = node_failed(conn, "Reason=No private key was specified\n");
			goto bailout;
		}
		if (0 != (rc = is_valid_ssk(&pub, private))) {
			rc = node_failed(conn, "Reason=No valid private key '%s'\n",
				private);
			goto bailout;
		}
		/* is_valid_ssk() already converted the private key to the public key */
		rc = node_success(conn, NULL, key_str(&pub), NULL);
	}

bailout:
	xfree(private);
	xfree(line);
	return rc;
}

int node_metadata(conn_t *conn, size_t segments,
	segment_header_t *sh, block_map_t *bm)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	static const char *description = "FEC84 SplitFile created by Entropy";
	static const char *mimetype = "application/octet-stream";
	size_t metasize = segments * 4096;	/* very arbitrary */
	char *metadata = NULL;
	char *dst;
	size_t i, j;
	int rc;
	FUN("node_metadata");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("allocating %#x bytes for metadata for %#x segments\n",
		(unsigned)metasize, (unsigned)segments));
	metadata = (char *)xcalloc(metasize,1);
	dst = metadata;
	dst += pm_spprintf(dst,metadata,metasize,"Version\n");
	dst += pm_spprintf(dst,metadata,metasize,"Revision=1\n");
	dst += pm_spprintf(dst,metadata,metasize,"EndPart\n");
	for (i = 0; i < segments; i++) {
		dst += pm_spprintf(dst,metadata,metasize,"Document\n");
		dst += pm_spprintf(dst,metadata,metasize,"Info.Description=%s\n",
			description);
		dst += pm_spprintf(dst,metadata,metasize,"Info.Format=%s\n",
			mimetype);
		dst += pm_spprintf(dst,metadata,metasize,"SplitFile.AlgoName=%s\n",
			sh[i].fec_algorithm);
		dst += pm_spprintf(dst,metadata,metasize,"SplitFile.Size=%x\n",
			sh[i].file_length);
		if (0 != sh[i].offset) {
			dst += pm_spprintf(dst,metadata,metasize,"SplitFile.Offset=%x\n",
				sh[i].offset);
		}
		dst += pm_spprintf(dst,metadata,metasize,"SplitFile.BlockCount=%x\n",
			sh[i].block_count);
		for (j = 0; j < sh[i].block_count; j++) {
			dst += pm_spprintf(dst,metadata,metasize,"SplitFile.Block.%x=%s\n",
				j, key_long(&bm[i].block[j]));
		}
		dst += pm_spprintf(dst,metadata,metasize,"SplitFile.CheckBlockCount=%x\n",
			sh[i].check_block_count);
		for (j = 0; j < sh[i].check_block_count; j++) {
			dst += pm_spprintf(dst,metadata,metasize,"SplitFile.CheckBlock.%x=%s\n",
				j, key_long(&bm[i].check[j]));
		}
		if (i + 1 < segments) {
			dst += pm_spprintf(dst,metadata,metasize,"EndPart\n");
		} else {
			dst += pm_spprintf(dst,metadata,metasize,"End\n");
		}
	}

	metasize = (size_t)(dst - metadata);
	LOGS(L_CLIENT,L_MINOR,("metadata length is %s\n", ull(metasize)));

	if (0 != (rc = node_data_chunk(conn, metadata, metasize))) {
		goto bailout;
	}

bailout:
	if (0 != rc) {
		rc = node_failed(conn, "Reason=Could not create metadata\n");
	}
	xfree(metadata);
	return rc;
}

int client_make_metadata(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	uint8_t *buff = NULL;
	char *line = NULL;
	char *description = NULL;
	char *checksum = NULL;
	char *mimetype = NULL;
	char *var, *val, *eol;
	chkey_t key;
	int32_t datalen = 0;
	uint32_t i;
	segment_header_t sh;
	block_map_t bm;
	segment_header_t *segment_headers = NULL;
	block_map_t *block_maps = NULL;
	size_t n, segments = 0;
	int rc = 0;
	FUN("client_make_metadata");
	(void)fcp;

	buff = (uint8_t *)xmalloc(FCP_BLOCKSIZE);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "Description")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Description=%s\n", val));
			xfree(description);
			description = xstrdup(val);
		} else if (0 == strcasecmp(var, "CheckSum")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckSum=%s\n", val));
			xfree(checksum);
			checksum = xstrdup(val);
		} else if (0 == strcasecmp(var, "MimeType")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: MimeType=%s\n", val));
			xfree(mimetype);
			mimetype = xstrdup(val);
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtol(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_MINOR,("got datalen=%x\n",
		(unsigned)datalen));

	while (datalen > 0) {

		/* receive the (next) segment header */
		while (0 == (rc = sock_agets(conn, &line))) {
			eol = strchr(line, '\r');
			if (NULL == eol)
				eol = strchr(line, '\n');
			if (NULL != eol)
				*eol = '\0';
			var = line;
			if (NULL != (val = strchr(var, '=')))
				*val++ = '\0';

			if (0 == strcasecmp(var, "EndMessage")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
				break;
			} else if (0 == strcasecmp(var, "SegmentHeader")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentHeader\n"));
				memset(&sh, 0, sizeof(sh));
			} else if (0 == strcasecmp(var, "FECAlgorithm")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: FECAlgorithm=%s\n", val));
				strncpy(sh.fec_algorithm, val, sizeof(sh.fec_algorithm));
			} else if (0 == strcasecmp(var, "FileLength")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: FileLength=%s\n", val));
				sh.file_length = strtoull(val, NULL, 16);
			} else if (0 == strcasecmp(var, "Offset")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: Offset=%s\n", val));
				sh.offset = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "BlockCount")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockCount=%s\n", val));
				sh.block_count = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "BlockSize")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockSize=%s\n", val));
				sh.block_size = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "CheckBlockCount")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockCount=%s\n", val));
				sh.check_block_count = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "CheckBlockSize")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockSize=%s\n", val));
				sh.check_block_size = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "Segments")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: Segments=%s\n", val));
				sh.segments = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "SegmentNum")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentNum=%s\n", val));
				sh.segment_num = strtoul(val, NULL, 16);
			} else if (0 == strcasecmp(var, "BlocksRequired")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: BlocksRequired=%s\n", val));
				sh.blocks_required = strtoul(val, NULL, 16);
			} else {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
			}
			xfree(line);
		}

		LOGS(L_CLIENT,L_MINOR,("expecting %x bytes block map data\n",
			(unsigned)datalen));
		/* receive the (next) block list */
		while (datalen > 0 && 0 == (rc = sock_agets(conn, &line))) {
			eol = strchr(line, '\r');
			if (NULL == eol)
				eol = strchr(line, '\n');
			if (NULL != eol)
				*eol = '\0';
			var = line;
			if (NULL != (val = strchr(var, '=')))
				*val++ = '\0';
	
			if (0 == strcasecmp(var, "EndMessage")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
				break;
			} else if (0 == strcasecmp(var, "BlockMap")) {
				LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockMap\n"));
				memset(&bm, 0, sizeof(bm));
			} else if (0 == strncmp(var, "Block.", 6)) {
				i = strtoul(var + 6, NULL, 16);
				LOGS(L_CLIENT,L_MINOR,("CLIENT: Block.%x=%s\n", i, val));
				if (i >= DATA_BLOCKS) {
					LOGS(L_CLIENT,L_ERROR,("CLIENT: block number error (%x)\n",
						i));
					rc = node_failed(conn,
						"Reason=Block index (%x) is out of range\n", i);
					goto bailout;
				}
				if (0 != (rc = is_valid_chk(&key, val))) {
					LOGS(L_CLIENT,L_ERROR,("CLIENT: block %x bad CHK@ (%s)\n",
						i, val));
					rc = node_failed(conn,
						"Reason=Block.%x has invalid CHK@ (%s)\n", i, val);
					goto bailout;
				}
				bm.block[i] = key;
			} else if (0 == strncmp(var, "Check.", 6)) {
				i = strtoul(var + 6, NULL, 16);
				LOGS(L_CLIENT,L_MINOR,("CLIENT: Check.%x=%s\n", i, val));
				if (i >= CHECK_BLOCKS) {
					LOGS(L_CLIENT,L_ERROR,("CLIENT: block number error (%x)\n",
						i));
					rc = node_failed(conn,
						"Reason=Check index (%x) is out of range\n", i);
					goto bailout;
				}
				if (0 != (rc = is_valid_chk(&key, val))) {
					LOGS(L_CLIENT,L_ERROR,("CLIENT: check %x bad CHK@ (%s)\n",
						i, val));
					rc = node_failed(conn,
						"Reason=Check.%x has invalid CHK@ (%s)\n", i, val);
					goto bailout;
				}
				bm.check[i] = key;
			}
		}

		/* first segment and blockmap received? */
		if (0 == segments) {
			segments = sh.segments;
			LOGS(L_CLIENT,L_MINOR,("allocating %#x segment hdrs & block maps\n",
				(unsigned)segments));
			segment_headers = (segment_header_t *)xcalloc(segments,
				sizeof(segment_header_t));
			block_maps = (block_map_t *)xcalloc(segments,
				sizeof(block_map_t));
		}

		if (sh.segment_num >= segments) {
			LOGS(L_CLIENT,L_ERROR,("CLIENT: segment_num %#x range (0..%#x)\n",
				(unsigned)sh.segment_num, (unsigned)(segments - 1)));
			rc = node_failed(conn,
				"Reason=SegmentNum (%#x) out of range (0..%#x)\n",
				(unsigned)sh.segment_num, (unsigned)(segments - 1));
			goto bailout;
		}
		segment_headers[sh.segment_num] = sh;
		block_maps[sh.segment_num] = bm;
		for (n = 0; n < segments; n++)
			if (n != segment_headers[n].segment_num)
				break;

		/* break out of the loop if all segments were transferred */
		if (n == segments)
			break;
	}
	
	if (0 == rc) {
		rc = node_metadata(conn, segments, segment_headers, block_maps);
	} else {
		rc = node_failed(conn, "Reason=Error creating meta data (%d)\n", rc);
	}

bailout:
	xfree(segment_headers);
	xfree(block_maps);
	xfree(description);
	xfree(checksum);
	xfree(mimetype);
	xfree(buff);
	xfree(line);
	return rc;
}

int node_success_sha1(conn_t *conn, sha1_digest_t *sha1)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_success_sha1");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: Success\n"));
	if (0 != (rc = sock_printf(conn, "Success\n"))) {
		return rc;
	}
	if (NULL != sha1) {
		LOGS(L_CLIENT,L_MINOR,("NODE: SHA1=%s\n", sha1_hexstr(sha1)));
		if (0 != (rc = sock_printf(conn, "SHA1=%s\n", sha1_hexstr(sha1)))) {
			return rc;
		}
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

int client_sha1(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	uint8_t *buff = NULL;
	char *line = NULL;
	char *var, *val, *eol;
	uint32_t datalen = 0;
	sha1_state_t ss;
	sha1_digest_t sha1;
	size_t size, fcp_size;
	int rc = 0;
	FUN("client_put");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_MINOR,("got datalen=%s\n",
		ull(datalen)));

	sha1_init(&ss);
	/* now receive the data */
	while (datalen > 0) {
		size = (datalen > fcp_size) ? fcp_size : datalen;
		if (0 != (rc = sock_readall(conn, buff, size))) {
			LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
				conn->peeraddr, buff, (unsigned)size, strerror(errno)));
			rc = -1;
			goto bailout;
		}
		LOGS(L_CLIENT,L_DEBUG,("got data (%#x bytes)\n", (unsigned)size));
		sha1_append(&ss, buff, size);
		datalen -= size;
	}
	sha1_finish(&ss, &sha1);

	if (0 == rc) {
		rc = node_success_sha1(conn, &sha1);
	} else {
		rc = node_failed(conn, "Reason=Error creating SHA1 (%d)\n", rc);
	}

bailout:
	xfree(buff);
	xfree(line);
	return rc;
}

int node_segment_header(conn_t *conn, segment_header_t *sh)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	int rc;
	FUN("node_segment_header");
	(void)fcp;

	LOGS(L_CLIENT,L_MINOR,("NODE: SegmentHeader\n"));
	if (0 != (rc = sock_printf(conn, "SegmentHeader\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: FECAlgorithm=%s\n", sh->fec_algorithm));
	if (0 != (rc = sock_printf(conn, "FECAlgorithm=%s\n", sh->fec_algorithm))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: FileLength=%s\n", ull(sh->file_length)));
	if (0 != (rc = sock_printf(conn, "FileLength=%s\n", ull(sh->file_length)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Offset=%s\n", ull(sh->offset)));
	if (0 != (rc = sock_printf(conn, "Offset=%s\n", ull(sh->offset)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: BlockCount=%s\n", ull(sh->block_count)));
	if (0 != (rc = sock_printf(conn, "BlockCount=%s\n", ull(sh->block_count)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: BlockSize=%s\n", ull(sh->block_size)));
	if (0 != (rc = sock_printf(conn, "BlockSize=%s\n", ull(sh->block_size)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: CheckBlockCount=%s\n", ull(sh->check_block_count)));
	if (0 != (rc = sock_printf(conn, "CheckBlockCount=%s\n", ull(sh->check_block_count)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: CheckBlockSize=%s\n", ull(sh->check_block_size)));
	if (0 != (rc = sock_printf(conn, "CheckBlockSize=%s\n", ull(sh->check_block_size)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: Segments=%s\n", ull(sh->segments)));
	if (0 != (rc = sock_printf(conn, "Segments=%s\n", ull(sh->segments)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: SegmentNum=%s\n", ull(sh->segment_num)));
	if (0 != (rc = sock_printf(conn, "SegmentNum=%s\n", ull(sh->segment_num)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: BlocksRequired=%s\n", ull(sh->blocks_required)));
	if (0 != (rc = sock_printf(conn, "BlocksRequired=%s\n", ull(sh->blocks_required)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	return 0;
}

int client_segment_file(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	char *line = NULL;
	char *algo_name = NULL;
	char *var, *val, *eol;
	uint64_t file_length = 0;
	size_t i, segments;
	segment_header_t *segment_headers = NULL;
	int rc = 0;
	FUN("client_segment_file");
	(void)fcp;

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "AlgoName")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: AlgoName=%s\n", val));
			algo_name = xstrdup(val);
		} else if (0 == strcasecmp(var, "FileLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FileLength=%s\n", val));
			file_length = strtoull(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}

	LOGS(L_CLIENT,L_MINOR,("got algo_name='%s', file_length=%s\n",
		algo_name, ull(file_length)));

	/* create 128MB file segments */
	segments = (size_t)((file_length + SIZE_MB(128) - 1) / SIZE_MB(128));
	segment_headers = (segment_header_t *)
		xcalloc(segments, sizeof(segment_header_t));

	for (i = 0; i < segments; i++) {
		segment_header_t *sh = &segment_headers[i];
		size_t segment_size = SIZE_MB(128);

		/* last segment may be shorter */	
		if (i + 1 == segments) {
			segment_size = file_length - SIZE_MB(128) * i;
			/* round to next KB */
			segment_size = (segment_size + SIZE_KB(1) - 1) &
				~(SIZE_KB(1) - 1);
		}

		/* ignore algo name for now */
		strncpy(sh->fec_algorithm, ALGO_NAME,
			sizeof(sh->fec_algorithm));
		sh->file_length = file_length;
		sh->offset = SIZE_MB(128) * i;
		/* block size is always segment_size / 8 */
		sh->block_size = segment_size / 8;
		sh->block_count = 8;
		/* check blocks are same size */
		sh->check_block_count = 4;
		sh->check_block_size = sh->block_size;
		sh->segments = segments;
		sh->segment_num = i;
		sh->blocks_required = sh->block_count;
		if (0 != (rc = node_segment_header(conn, sh))) {
			break;
		}
	}

	if (0 != rc) {
		rc = node_failed(conn, "Reason=Error sending segment header(s)\n");
	}

	xfree(line);
	xfree(segment_headers);
	xfree(algo_name);
	return rc;
}

int node_blocks_encoded(conn_t *conn, size_t block_count, size_t block_size,
	uint32_t list[], char **tmp_file)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	void *buff = NULL;
	uint64_t offs;
	size_t i, n;
	int fd = -1, rc;
	size_t fcp_size;
	FUN("node_blocks_encoded");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	LOGS(L_CLIENT,L_MINOR,("NODE: BlocksEncoded\n"));
	if (0 != (rc = sock_printf(conn, "BlocksEncoded\n"))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: BlockCount=%s\n", ull(block_count)));
	if (0 != (rc = sock_printf(conn, "BlockCount=%s\n", ull(block_count)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: BlockSize=%s\n", ull(block_size)));
	if (0 != (rc = sock_printf(conn, "BlockSize=%s\n", ull(block_size)))) {
		return rc;
	}
	LOGS(L_CLIENT,L_MINOR,("NODE: EndMessage\n"));
	if (0 != (rc = sock_printf(conn,"EndMessage\n"))) {
		return rc;
	}

	buff = (void *)xmalloc(fcp_size);

	for (i = 0; i < block_count; i++) {
		n = list[i];

		fd = open(tmp_file[n], O_RD);
		if (-1 == fd) {
			LOGS(L_CLIENT,L_ERROR,("open('%s', O_RD) call failed (%s)\n",
				tmp_file[n], strerror(errno)));
			goto bailout;
		}

		/* send the file down to the client */
		for (offs = 0; offs < block_size; /* */) {
			size_t size, done;
			if (offs + fcp_size > block_size) {
				size = (size_t)(block_size - offs);
			} else {
				size = fcp_size;
			}
			if (size != (done = read(fd, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("read(%d,%p,%#x) from %s failed (%s)\n",
					fd, buff, (unsigned)size,
					tmp_file[n], strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %#x bytes data\n", (unsigned)done));
			if (0 != (rc = node_data_chunk(conn, buff, done))) {
				LOGS(L_CLIENT,L_ERROR,("node_data_chunk call failed!\n"));
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("sent %#x bytes data to %s\n",
				(unsigned)done, conn->peeraddr));
			offs += done;
		}
		close(fd);
		fd = -1;
	}

bailout:
	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	xfree(buff);
	return rc;
}

int client_encode_segment(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	uint8_t *buff = NULL;
	char *tmp_file[TOTAL_BLOCKS] =
		{NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
	char *line = NULL;
	char *requested_list = NULL;
	char *blk;
	char *var, *val, *eol;
	uint32_t *requested_block = NULL;
	size_t requested_block_count;
	uint64_t datalen = 0;
	uint64_t offs;
	uint8_t *data = NULL;
	size_t i, done, size;
	segment_header_t sh;
	int fd[TOTAL_BLOCKS] =
		{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
	size_t fcp_size;
	int rc = 0;
	FUN("client_encode_segment");
	(void)fcp;

	memset(&sh, 0, sizeof(sh));

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	buff = (uint8_t *)xmalloc(fcp_size);

	/* allocate space for temp filenames */
	for (i = 0; i < TOTAL_BLOCKS; i++) {
		tmp_file[i] = (char *)xcalloc(MAXPATHLEN, sizeof(char));
	}

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "RequestedList")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: RequestedList=%s\n", val));
			requested_list = xstrdup(val);
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtoull(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_NORMAL,("got requested_list='%s', datalen=%s\n",
		requested_list ? requested_list : "all", ull(datalen)));

	while (0 == (rc = sock_agets(conn, &line))) {
		/* subtract this from datalen (there is no metadatalen!?) */
		datalen -= strlen(line);

		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "SegmentHeader")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentHeader\n"));
			memset(&sh, 0, sizeof(sh));
		} else if (0 == strcasecmp(var, "FECAlgorithm")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECAlgorithm=%s\n", val));
			strncpy(sh.fec_algorithm, val, sizeof(sh.fec_algorithm));
		} else if (0 == strcasecmp(var, "FileLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FileLength=%s\n", val));
			sh.file_length = strtoull(val, NULL, 16);
		} else if (0 == strcasecmp(var, "Offset")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Offset=%s\n", val));
			sh.offset = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlockCount")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockCount=%s\n", val));
			sh.block_count = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlockSize")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockSize=%s\n", val));
			sh.block_size = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "CheckBlockCount")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockCount=%s\n", val));
			sh.check_block_count = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "CheckBlockSize")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockSize=%s\n", val));
			sh.check_block_size = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "Segments")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Segments=%s\n", val));
			sh.segments = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "SegmentNum")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentNum=%s\n", val));
			sh.segment_num = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlocksRequired")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlocksRequired=%s\n", val));
			sh.blocks_required = strtoul(val, NULL, 16);
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_NORMAL,("got segment header\n"));

	/* sanity checks; and checks specific for entropy_fec_8_4 */
	if (0 != strcasecmp(sh.fec_algorithm, ALGO_NAME)) {
		LOGS(L_CLIENT,L_ERROR,("unsupported FECAlgorithm '%s'\n",
			sh.fec_algorithm));
		rc = node_failed(conn,
			"Reason=Unsupported FECAlgorithm '%s'\n", sh.fec_algorithm);
		goto bailout;
	}
	if (0 == sh.file_length) {
		LOGS(L_CLIENT,L_ERROR,("FileLength is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=FileLength is zero or unspecified\n");
		goto bailout;
	}
	if (0 == sh.block_count) {
		LOGS(L_CLIENT,L_ERROR,("BlockCount is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=BlockCount is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 always has 8 data blocks */
	if (DATA_BLOCKS != sh.block_count) {
		LOGS(L_CLIENT,L_ERROR,("BlockCount is not %#x (%#x)\n",
			DATA_BLOCKS, (unsigned)sh.block_count));
		rc = node_failed(conn,
			"Reason=BlockCount is not %#x (%#x)\n",
			DATA_BLOCKS, (unsigned)sh.block_count);
		goto bailout;
	}
	if (0 == sh.block_size) {
		LOGS(L_CLIENT,L_ERROR,("BlockSize is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=BlockSize is zero or unspecified\n");
		goto bailout;
	}
	if (0 == sh.check_block_count) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockCount is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=CheckBlockCount is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 always has 4 check blocks */
	if (CHECK_BLOCKS != sh.check_block_count) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockCount is not %#x (%#x)\n",
			CHECK_BLOCKS, (unsigned)sh.check_block_count));
		rc = node_failed(conn,
			"Reason=CheckBlockCount is not %#x (%#x)\n",
			CHECK_BLOCKS, (unsigned)sh.check_block_count);
		goto bailout;
	}
	if (0 == sh.check_block_size) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockSize is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=CheckBlockSize is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 check blocks always have the same size as data blocks */
	if (sh.block_size != sh.check_block_size) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockSize (%#x) != BlockSize (%#x)\n",
			(unsigned)sh.check_block_size, (unsigned)sh.block_size));
		rc = node_failed(conn,
			"Reason=CheckBlockSize (%#x) is not equal to BlockSize (%#x)\n",
			(unsigned)sh.check_block_size, (unsigned)sh.block_size);
		goto bailout;
	}
	if (0 == sh.segments) {
		LOGS(L_CLIENT,L_ERROR,("Segments is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=Segments is zero or unspecified\n");
		goto bailout;
	}

	for (i = 0; i < TOTAL_BLOCKS; i++) {
		char fecX[4+1];
		pm_snprintf(fecX, sizeof(fecX), "fec%x", (unsigned)i);
		fd[i] = mkstemp_binary(tmp_file[i], fecX);
		if (-1 == fd[i]) {
			LOGS(L_CLIENT,L_ERROR,("mkstemp_binary(%s,%s) call failed (%s)\n",
				tmp_file[i], fecX, strerror(errno)));
			rc = -1;
			goto bailout;
		}
		/* make sparse files of size sh.block_size */
		lseek(fd[i], sh.block_size - 1, SEEK_SET);
		if (1 != write(fd[i], "\0", 1)) {
			LOGS(L_CLIENT,L_ERROR,("write(...,1) at end of block failed (%s)\n",
				strerror(errno)));
			rc = -1;
			goto bailout;
		}
		lseek(fd[i], 0, SEEK_SET);
	}

	/* now receive the data */
	datalen = 0;
	for (i = 0; i < sh.block_count; i++) {
		for (offs = 0; offs < sh.block_size; /* */) {
			size = fcp_size;
			if (offs + size > sh.block_size) {
				size = sh.block_size - offs;
			}
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %s bytes\n", ull(size)));
			if (size != (size_t)write(fd[i], buff, size)) {
				LOGS(L_CLIENT,L_ERROR,("write(%d,%p,%#x) to %s failed (%s)\n",
					fd[i], buff, (unsigned)size,
					tmp_file[i], strerror(errno)));
				rc = node_failed(conn,
					"Reason=Temp storage write error\n");
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("wrote %#x bytes data to '%s'\n",
				(unsigned)size, tmp_file[i]));
			offs += size;
			datalen += size;
		}
	}

	for (i = 0; i < sh.block_count; i++) {
		close(fd[i]);
		fd[i] = -1;
	}
	LOGS(L_CLIENT,L_NORMAL,("got %s bytes data\n", ull(datalen)));

	/* now look at which check blocks are requested */
	requested_block = (uint32_t *)
		xcalloc(sizeof(uint32_t), sh.check_block_count);
	if (NULL == requested_list || 0 == strlen(requested_list)) {
		/* request all check blocks */
		requested_block_count = sh.check_block_count;
		for (i = 0; i < requested_block_count; i++) {
			requested_block[i] = i;
		}
	} else {
		requested_block_count = 0;
		blk = strtok(requested_list, ",");
		while (NULL != blk) {
			requested_block[requested_block_count++] =
				strtoul(blk, NULL, 16);
			blk = strtok(NULL, ",");
		}
	}

	/* allocate buffers for FEC encoder */
	data = (uint8_t *)xcalloc(TOTAL_BLOCKS, fcp_size);

	for (i = 0; i < sh.block_count; i++) {
		fd[i] = open(tmp_file[i], O_RD);
		if (-1 == fd[i]) {
			LOGS(L_CLIENT,L_ERROR,("open('%s', O_RD) call failed (%s)\n",
				tmp_file[i], strerror(errno)));
			goto bailout;
		}
	}

	for (offs = 0; offs < sh.block_size; offs += fcp_size) {
		for (i = 0; i < sh.block_count; i++) {
			size = fcp_size;
			if (offs + size > sh.block_size) {
				size = (size_t)(sh.block_size - offs);
			}
			/* read the data for the 8 blocks */
			done = read(fd[i], &data[i * fcp_size], size);
			if (size != done) {
				LOGS(L_CLIENT,L_ERROR,("read(%d,%p,%#x) from %s (%s)\n",
					fd[i], &data[i * fcp_size], (unsigned)size,
					tmp_file[i], strerror(errno)));
				rc = -1;
				goto bailout;
			}
		}
		/* now create and write the FEC blocks */
		for (i = 0; i < requested_block_count; i++) {
			size_t n = sh.block_count + requested_block[i];
			size = fcp_size;
			if (offs + size > sh.block_size) {
				size = (size_t)(sh.block_size - offs);
			}

			fec_encode(data, size, requested_block[i],
				&data[n * fcp_size]);

			/* write the check data */
			if (size != (done = write(fd[n], &data[n * fcp_size], size))) {
				LOGS(L_CLIENT,L_ERROR,("write(%d,%p,%#x) to %s failed (%s)\n",
					fd[n], &data[n * fcp_size], (unsigned)size,
					tmp_file[n], strerror(errno)));
				rc = -1;
				goto bailout;
			}
			/* sleep 1/2us per byte, that is approximately 0.5s per MB */
			osd_usleep(size/2);
		}
	}
	/* all is done... */

	for (i = 0; i < TOTAL_BLOCKS; i++) {
		if (-1 != fd[i]) {
			close(fd[i]);
			fd[i] = -1;
		}
	}

	if (0 == rc) {
		rc = node_blocks_encoded(conn, requested_block_count,
			sh.check_block_size, requested_block, tmp_file);
	} else {
		rc = node_failed(conn, "Reason=Huh?\n");
	}

bailout:
	for (i = 0; i < TOTAL_BLOCKS; i++) {
		if (-1 != fd[i]) {
			close(fd[i]);
			fd[i] = -1;
		}
		if (NULL != tmp_file[i] && '\0' != tmp_file[i][0]) {
			unlink(tmp_file[i]);
			tmp_file[i][0] = '\0';
		}
		xfree(tmp_file[i]);
	}
	xfree(data);
	xfree(buff);
	xfree(line);
	xfree(requested_list);
	xfree(requested_block);
	return rc;
}

int client_decode_segment(conn_t *conn)
{
	fcp_proto_t *fcp = (fcp_proto_t *)conn->temp;
	uint8_t *buff = NULL;
	char *tmp_file[TOTAL_BLOCKS] =
		{NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
	char *line = NULL;
	char *toke = NULL;
	char *block_list = NULL;
	char *check_block_list = NULL;
	char *requested_list = NULL;
	char *blk;
	char *var, *val, *eol;
	size_t block_count;
	uint32_t *block = NULL;
	size_t check_block_count;
	uint32_t *check_block = NULL;
	size_t requested_block_count;
	uint32_t *requested_block = NULL;
	uint64_t datalen = 0;
	uint64_t offs;
	uint8_t *data = NULL;
	uint8_t *src[TOTAL_BLOCKS];
	size_t i, done, size;
	segment_header_t sh;
	int fd[TOTAL_BLOCKS] =
		{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
	int idx[TOTAL_BLOCKS] =
		{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
	int n;
	size_t fcp_size;
	int rc = 0;
	FUN("client_decode_segment");
	(void)fcp;

	/* try to use optimized block size for the socket */
	if (0 == conn->maxsize || conn->maxsize > FCP_BLOCKSIZE) {
		fcp_size = FCP_BLOCKSIZE;
	} else {
		fcp_size = conn->maxsize;
	}

	memset(&sh, 0, sizeof(sh));

	buff = (uint8_t *)xmalloc(fcp_size);

	/* allocate space for temp filenames */
	for (i = 0; i < TOTAL_BLOCKS; i++) {
		tmp_file[i] = (char *)xcalloc(MAXPATHLEN, sizeof(char));
	}

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "BlockList")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockList=%s\n", val));
			block_list = xstrdup(val);
		} else if (0 == strcasecmp(var, "CheckList")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckList=%s\n", val));
			check_block_list = xstrdup(val);
		} else if (0 == strcasecmp(var, "RequestedList")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: RequestedList=%s\n", val));
			requested_list = xstrdup(val);
		} else if (0 == strcasecmp(var, "DataLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: DataLength=%s\n", val));
			datalen = strtoull(val, NULL, 16);
		} else if (0 == strcasecmp(var, "KeepAlive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: KeepAlive=%s\n", val));
			fcp->keep_alive = boolean(val);
		} else if (0 == strcasecmp(var, "Data")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Data\n"));
			break;
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_NORMAL,("got requested_list='%s', datalen=%s\n",
		requested_list ? requested_list : "all", ull(datalen)));

	if (NULL == block_list) {
		LOGS(L_CLIENT,L_ERROR,("got no BlockList header\n"));
		rc = node_failed(conn,
			"Reason=No BlockList found in header\n");
		goto bailout;
	}

	if (NULL == check_block_list) {
		LOGS(L_CLIENT,L_ERROR,("got no CheckList header\n"));
		rc = node_failed(conn,
			"Reason=No CheckList found in header\n");
		goto bailout;
	}

	if (NULL == requested_list) {
		LOGS(L_CLIENT,L_ERROR,("got no RequestedList header\n"));
		rc = node_failed(conn,
			"Reason=No RequestedList found in header\n");
		goto bailout;
	}

	while (0 == (rc = sock_agets(conn, &line))) {
		/* subtract this from datalen (there is no metadatalen!?) */
		datalen -= strlen(line);

		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';
		var = line;
		if (NULL != (val = strchr(var, '=')))
			*val++ = '\0';

		if (0 == strcasecmp(var, "EndMessage")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: EndMessage\n"));
			break;
		} else if (0 == strcasecmp(var, "SegmentHeader")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentHeader\n"));
			memset(&sh, 0, sizeof(sh));
		} else if (0 == strcasecmp(var, "FECAlgorithm")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECAlgorithm=%s\n", val));
			strncpy(sh.fec_algorithm, val, sizeof(sh.fec_algorithm));
		} else if (0 == strcasecmp(var, "FileLength")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FileLength=%s\n", val));
			sh.file_length = strtoull(val, NULL, 16);
		} else if (0 == strcasecmp(var, "Offset")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Offset=%s\n", val));
			sh.offset = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlockCount")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockCount=%s\n", val));
			sh.block_count = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlockSize")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlockSize=%s\n", val));
			sh.block_size = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "CheckBlockCount")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockCount=%s\n", val));
			sh.check_block_count = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "CheckBlockSize")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: CheckBlockSize=%s\n", val));
			sh.check_block_size = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "Segments")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: Segments=%s\n", val));
			sh.segments = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "SegmentNum")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: SegmentNum=%s\n", val));
			sh.segment_num = strtoul(val, NULL, 16);
		} else if (0 == strcasecmp(var, "BlocksRequired")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: BlocksRequired=%s\n", val));
			sh.blocks_required = strtoul(val, NULL, 16);
		} else {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: %s\n", line));
		}
		xfree(line);
	}
	LOGS(L_CLIENT,L_NORMAL,("got segment header\n"));

	/* sanity checks; and checks specific for entropy_fec_8_4 */
	if (0 != strcasecmp(sh.fec_algorithm, ALGO_NAME)) {
		LOGS(L_CLIENT,L_ERROR,("unsupported FECAlgorithm '%s'\n",
			sh.fec_algorithm));
		rc = node_failed(conn,
			"Reason=Unsupported FECAlgorithm '%s'\n", sh.fec_algorithm);
		goto bailout;
	}
	if (0 == sh.file_length) {
		LOGS(L_CLIENT,L_ERROR,("FileLength is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=FileLength is zero or unspecified\n");
		goto bailout;
	}
	if (0 == sh.block_count) {
		LOGS(L_CLIENT,L_ERROR,("BlockCount is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=BlockCount is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 always has 8 data blocks */
	if (DATA_BLOCKS != sh.block_count) {
		LOGS(L_CLIENT,L_ERROR,("BlockCount is not %#x (%#x)\n",
			DATA_BLOCKS, (unsigned)sh.block_count));
		rc = node_failed(conn,
			"Reason=BlockCount is not %#x (%#x)\n",
			DATA_BLOCKS, (unsigned)sh.block_count);
		goto bailout;
	}
	if (0 == sh.block_size) {
		LOGS(L_CLIENT,L_ERROR,("BlockSize is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=BlockSize is zero or unspecified\n");
		goto bailout;
	}
	if (0 == sh.check_block_count) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockCount is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=CheckBlockCount is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 always has 4 check blocks */
	if (CHECK_BLOCKS != sh.check_block_count) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockCount is not %#x (%#x)\n",
			CHECK_BLOCKS, (unsigned)sh.check_block_count));
		rc = node_failed(conn,
			"Reason=CheckBlockCount is not 4 (%#x)\n",
			(unsigned)sh.check_block_count);
		goto bailout;
	}
	if (0 == sh.check_block_size) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockSize is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=CheckBlockSize is zero or unspecified\n");
		goto bailout;
	}
	/* entropy_fec_8_4 check blocks always have the same size as data blocks */
	if (sh.block_size != sh.check_block_size) {
		LOGS(L_CLIENT,L_ERROR,("CheckBlockSize (%#x) != BlockSize (%#x)\n",
			(unsigned)sh.check_block_size, (unsigned)sh.block_size));
		rc = node_failed(conn,
			"Reason=CheckBlockSize (%#x) is not equal to BlockSize (%#x)\n",
			(unsigned)sh.check_block_size, (unsigned)sh.block_size);
		goto bailout;
	}
	if (0 == sh.segments) {
		LOGS(L_CLIENT,L_ERROR,("Segments is zero or unspecified\n"));
		rc = node_failed(conn,
			"Reason=Segments is zero or unspecified\n");
		goto bailout;
	}

	/* look at which check blocks are requested */
	block = (uint32_t *)
		xcalloc(sizeof(uint32_t), sh.block_count);
	block_count = 0;
	blk = strtok(block_list, ",");
	while (NULL != blk && block_count < sh.block_count) {
		block[block_count++] = strtoul(blk, NULL, 16);
		blk = strtok(NULL, ",");
	}

	check_block = (uint32_t *)
		xcalloc(sizeof(uint32_t), sh.check_block_count);
	check_block_count = 0;
	blk = strtok(check_block_list, ",");
	while (NULL != blk && check_block_count < sh.check_block_count) {
		check_block[check_block_count++] = strtoul(blk, NULL, 16);
		blk = strtok(NULL, ",");
	}

	requested_block = (uint32_t *)
		xcalloc(sizeof(uint32_t), sh.block_count + sh.check_block_count);
	requested_block_count = 0;
	blk = strtok(requested_list, ",");
	while (NULL != blk &&
		requested_block_count < sh.block_count + sh.check_block_count) {
		requested_block[requested_block_count++] = strtoul(blk, NULL, 16);
		blk = strtok(NULL, ",");
	}

	for (i = 0; i < TOTAL_BLOCKS; i++) {
		char fecX[4+1];
		pm_snprintf(fecX, sizeof(fecX), "fec%x", (unsigned)i);
		fd[i] = mkstemp_binary(tmp_file[i], fecX);
		if (-1 == fd[i]) {
			LOGS(L_CLIENT,L_ERROR,("mkstemp_binary(%s,%s) call failed (%s)\n",
				tmp_file[i], fecX, strerror(errno)));
			rc = -1;
			goto bailout;
		}
		/* make sparse files of size sh.block_size */
		lseek(fd[i], sh.block_size - 1, SEEK_SET);
		if (1 != write(fd[i], "\0", 1)) {
			LOGS(L_CLIENT,L_ERROR,("write(...,1) at end of block failed (%s)\n",
				strerror(errno)));
			rc = -1;
			goto bailout;
		}
		lseek(fd[i], 0, SEEK_SET);
	}

	/* now receive the data and check blocks */
	for (i = 0; i < block_count + check_block_count; i++) {
		/* index 0 .. block_count-1 are data blocks */
		if (i < block_count) {
			/* get the data block index */
			n = block[i];
		} else {
			/* get the check block index */
			n = DATA_BLOCKS + check_block[i - block_count];
		}
		/* set block index for fec_decode() call */
		idx[i] = n;
		for (offs = 0; offs < sh.block_size; /* */) {
			size = (datalen > fcp_size) ? fcp_size : datalen;
			if (0 != (rc = sock_readall(conn, buff, size))) {
				LOGS(L_CLIENT,L_ERROR,("sock_readall(%s,%p,%#x) failed (%s)\n",
					conn->peeraddr, buff, (unsigned)size, strerror(errno)));
				rc = -1;
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("got %s bytes\n", ull(size)));
			if (size != (size_t)write(fd[n], buff, size)) {
				LOGS(L_CLIENT,L_ERROR,("write(%d,%p,%#x) to %s failed (%s)\n",
					fd[n], buff, (unsigned)size,
					tmp_file[n], strerror(errno)));
				rc = node_failed(conn,
					"Reason=Temp storage write error\n");
				goto bailout;
			}
			LOGS(L_CLIENT,L_DEBUG,("wrote %#x bytes data to '%s'\n",
				(unsigned)size, tmp_file[n]));
			offs += size;
		}
		/* seek back to the beginning of the file */
		lseek(fd[n], 0, SEEK_SET);
	}

	LOGS(L_CLIENT,L_NORMAL,("got %s bytes data\n", ull(datalen)));

	/* allocate buffers for FEC decoder */
	data = (uint8_t *)xcalloc(TOTAL_BLOCKS, fcp_size);
	for (n = 0; n < TOTAL_BLOCKS; n++)
		src[n] = &data[n * fcp_size];

	for (offs = 0; offs < sh.block_size; offs += fcp_size) {
		if (offs + fcp_size > sh.block_size) {
			size = (size_t)(sh.block_size - offs);
		} else {
			size = fcp_size;
		}

		for (i = 0; i < block_count + check_block_count; i++) {
			if (i < block_count) {
				/* get the data block index */
				n = block[i];
			} else {
				/* get the check block index */
				n = DATA_BLOCKS + check_block[i - block_count];
			}
			if (size != (done = read(fd[n], &data[n * fcp_size], size))) {
				LOGS(L_CLIENT,L_ERROR,("read(%d,%p,%#x) from %s (%s)\n",
					fd[n], &data[n * fcp_size], (unsigned)size,
					tmp_file[n], strerror(errno)));
				rc = -1;
				goto bailout;
			}
		}

		/* FEC decode the missing data blocks */
		rc = fec_decode(data, src, size, idx);

		/* now write requested data blocks */
		for (i = 0; i < requested_block_count; i++) {
			n = requested_block[i];

			/* if this is a check block that is requested, encode it now */
			if (n >= DATA_BLOCKS) {
				fec_encode(data, size, n - DATA_BLOCKS, &data[n * fcp_size]);
			}
			if (size != (done = write(fd[n], &data[n * fcp_size], size))) {
				LOGS(L_CLIENT,L_ERROR,("write(%d,%p,%#x) to %s failed (%s)\n",
					fd[n], &data[n * fcp_size], (unsigned)size,
					tmp_file[n], strerror(errno)));
				rc = -1;
				goto bailout;
			}
		}
		/* sleep 1/8us per byte, that is 0.131072s per MB */
		osd_usleep(size/8);
	}
	/* close the the temp files */
	for (i = 0; i < TOTAL_BLOCKS; i++) {
		if (-1 != fd[i]) {
			close(fd[i]);
			fd[i] = -1;
		}
	}

	if (0 == rc) {
		rc = node_blocks_encoded(conn, requested_block_count,
			sh.check_block_size, requested_block, tmp_file);
	} else {
		rc = node_failed(conn, "Reason=Huh?\n");
	}

bailout:
	for (i = 0; i < TOTAL_BLOCKS; i++) {
		if (-1 != fd[i]) {
			close(fd[i]);
			fd[i] = -1;
		}
		if (NULL != tmp_file[i] && '\0' != tmp_file[i][0]) {
			unlink(tmp_file[i]);
		}
		xfree(tmp_file[i]);
	}
	xfree(data);
	xfree(buff);
	xfree(toke);
	xfree(line);
	xfree(block_list);
	xfree(block);
	xfree(check_block_list);
	xfree(check_block);
	xfree(requested_list);
	xfree(requested_block);
	return rc;
}

static void client_child(conn_t *conn)
{
	fcp_proto_t *fcp = NULL;
	char *line = NULL;
	char *eol;
	size_t size;
	int rc;
	FUN("client_child");

	fcp = (fcp_proto_t *)xcalloc(1, sizeof(fcp_proto_t));
	size = sizeof(fcp->header);
	conn->temp = fcp;
	if (0 != (rc = sock_readall(conn, fcp->header, size))) {
		LOGS(L_CLIENT,L_MINOR,("sock_readall(%s,%p,%#x) header failed (%s)\n",
			conn->peeraddr, line, (unsigned)size, strerror(errno)));
		conn->temp = NULL;
		xfree(fcp);
		sock_shutdown(conn);
		osd_exit(rc);
	}

	if (0x00 == fcp->header[2] && 0x02 == fcp->header[3]) {
		fcp->proto = FCP_FREENET;
	} else if (0x01 == fcp->header[2] && 0x02 == fcp->header[3]) {
		fcp->proto = FCP_ENTROPY;
	} else {
		LOGS(L_CLIENT,L_ERROR,("invalid proto header: %02x %02x %02x %02x\n", 
			fcp->header[0], fcp->header[1], fcp->header[2], fcp->header[3]));
	}

	while (0 == (rc = sock_agets(conn, &line))) {
		eol = strchr(line, '\r');
		if (NULL == eol)
			eol = strchr(line, '\n');
		if (NULL != eol)
			*eol = '\0';

		if (0 == strcasecmp(line, "ClientHello")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientHello\n"));
			rc = client_hello(conn);
		} else if (0 == strcasecmp(line, "ClientInfo")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientInfo\n"));
			rc = client_info(conn);
		} else if (0 == strcasecmp(line, "ClientGet")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientGet\n"));
			rc = client_get(conn);
		} else if (0 == strcasecmp(line, "ClientPut")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientPut\n"));
			rc = client_put(conn);
		} else if (0 == strcasecmp(line, "GenerateCHK")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: GenerateCHK\n"));
			rc = client_chk(conn);
		} else if (0 == strcasecmp(line, "ClientDelete")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientDelete\n"));
			rc = client_del(conn);
		} else if (0 == strcasecmp(line, "GenerateSVKPair")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: GenerateSVKPair\n"));
			rc = client_generate_svk_pair(conn);
		} else if (0 == strcasecmp(line, "InvertPrivateKey")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: InvertPrivateKey\n"));
			rc = client_invert_private_key(conn);
		} else if (0 == strcasecmp(line, "FECSegmentFile")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECSegmentFile\n"));
			rc = client_segment_file(conn);
		} else if (0 == strcasecmp(line, "FECEncodeSegment")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECEncodeSegment\n"));
			rc = client_encode_segment(conn);
		} else if (0 == strcasecmp(line, "FECDecodeSegment")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECDecodeSegment\n"));
			rc = client_decode_segment(conn);
		} else if (0 == strcasecmp(line, "FECMakeMetadata")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: FECMakeMetadata\n"));
			rc = client_make_metadata(conn);
		} else if (0 == strcasecmp(line, "GenerateSHA1")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: GenerateSHA1\n"));
			rc = client_sha1(conn);
		} else if (0 == strcasecmp(line, "ClientBroadcast")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientBroadcast\n"));
			rc = client_broadcast(conn);
		} else if (0 == strcasecmp(line, "ClientReceive")) {
			LOGS(L_CLIENT,L_MINOR,("CLIENT: ClientReceive\n"));
			rc = client_receive(conn);
		} else {
			LOGS(L_CLIENT,L_ERROR,("CLIENT: unknown command '%s'\n", line));
			rc = node_format_error(conn, "Unknown Command: '%s'\n", line);
		}
		xfree(line);
		/* break out and shut down if keep alive flag was not set */
		if (0 == fcp->keep_alive) {
			break;
		}
		/* Keep going for FCP_ENTROPY connections */
	}

	if (0 != rc) {
		LOGS(L_CLIENT,L_DEBUG,("client returned %d\n", rc));
	}

	xfree(fcp);
	conn->temp = NULL;
	sock_shutdown(conn);
	osd_exit(rc);
}

void client_exit(int sig)
{
	pid_t pid = getpid();
	FUN("client_exit");
    
	signal(sig, SIG_DFL);
	LOGS(L_CLIENT,L_MINOR,("*** {%d} signal %s ***\n",
		(int)pid, signal_name(sig)));
	if (pid == g_client_pid) {
		g_client_pid = -1;
	}
}

int client_accept(void)
{
	FUN("client_accept");
	if ((pid_t)-1 == g_client_pid ||
		(pid_t)0 == g_client_pid) {
		LOGS(L_CLIENT,L_NORMAL,("Detected shut down condition\n"));
		return -1;
	}
	return 0;
}

int client(void)
{
	char *listener = NULL;
	int rc = 0;
	FUN("client");

	rc = pm_asprintf(&listener, "client [%s:%d]",
		g_conf->fcphost, g_conf->fcpport);
	if (-1 == rc) {
		LOGS(L_CLIENT,L_ERROR,("pm_asprintf() call failed (%s)",
			strerror(errno)));
		return rc;
	}
	rc = 0;

	switch (osd_fork2(listener, g_conf->niceness, -1)) {
	case -1:
		LOGS(L_CLIENT,L_ERROR,("osd_fork2('%s',%d,-1) call failed (%s)",
			listener, g_conf->niceness, strerror(errno)));
		xfree(listener);
		return -1;
	case 0:
		g_client_pid = getpid();
    	/* capture signals */
		set_signal_handler(SIGHUP, client_exit);
		set_signal_handler(SIGINT, client_exit);
		set_signal_handler(SIGPIPE, client_exit);
		set_signal_handler(SIGALRM, client_exit);
		set_signal_handler(SIGTERM, client_exit);
		rc = sock_incoming(g_conf->fcphost, g_conf->fcpport,
			0, client_child, client_accept, 0, listener);
		if (0 != rc) {
			LOGS(L_CLIENT,L_ERROR,("sock_incoming(%s:%d) call failed (%s)\n",
				g_conf->fcphost, g_conf->fcpport, strerror(errno)));
		}
		osd_exit(rc);
	}

	xfree(listener);
	info("%s:%d ", g_conf->fcphost, g_conf->fcpport);
	return rc;
}
